// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES2/gl2extchromium.h>

#include "gpu/command_buffer/tests/gl_manager.h"
#include "gpu/command_buffer/tests/gl_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#define SHADER(Src) #Src

namespace gpu {

class GLVirtualContextsTest : public testing::Test {
protected:
    static const int kSize0 = 4;
    static const int kSize1 = 8;
    static const int kSize2 = 16;

    static const GLfloat kFloatRed[4];
    static const GLfloat kFloatGreen[4];
    static const uint8 kExpectedRed[4];
    static const uint8 kExpectedGreen[4];

    void SetUp() override
    {
        GLManager::Options options;
        options.size = gfx::Size(kSize0, kSize0);
        gl_real_.Initialize(options);
        gl_real_shared_.Initialize(options);
        options.virtual_manager = &gl_real_shared_;
        options.size = gfx::Size(kSize1, kSize1);
        gl1_.Initialize(options);
        options.size = gfx::Size(kSize2, kSize2);
        gl2_.Initialize(options);
    }

    void TearDown() override
    {
        gl1_.Destroy();
        gl2_.Destroy();
        gl_real_shared_.Destroy();
        gl_real_.Destroy();
    }

    GLuint SetupColoredVertexProgram()
    {
        static const char* v_shader_str = SHADER(
            attribute vec4 a_position;
            attribute vec4 a_color;
            varying vec4 v_color;
            void main() {
                gl_Position = a_position;
                v_color = a_color;
            });

        static const char* f_shader_str = SHADER(
            precision mediump float;
            varying vec4 v_color;
            void main() {
                gl_FragColor = v_color;
            });

        GLuint program = GLTestHelper::LoadProgram(v_shader_str, f_shader_str);
        glUseProgram(program);
        return program;
    }

    void SetUpColoredUnitQuad(const GLfloat* color)
    {
        GLuint program1 = SetupColoredVertexProgram();
        GLuint position_loc1 = glGetAttribLocation(program1, "a_position");
        GLuint color_loc1 = glGetAttribLocation(program1, "a_color");
        GLTestHelper::SetupUnitQuad(position_loc1);
        GLTestHelper::SetupColorsForUnitQuad(color_loc1, color, GL_STATIC_DRAW);
    }

    GLManager gl_real_;
    GLManager gl_real_shared_;
    GLManager gl1_;
    GLManager gl2_;
};

const GLfloat GLVirtualContextsTest::kFloatRed[4] = {
    1.0f,
    0.0f,
    0.0f,
    1.0f,
};
const GLfloat GLVirtualContextsTest::kFloatGreen[4] = {
    0.0f,
    1.0f,
    0.0f,
    1.0f,
};
const uint8 GLVirtualContextsTest::kExpectedRed[4] = {
    255,
    0,
    0,
    255,
};
const uint8 GLVirtualContextsTest::kExpectedGreen[4] = {
    0,
    255,
    0,
    255,
};

namespace {

    void SetupSimpleShader(const uint8* color)
    {
        static const char* v_shader_str = SHADER(
            attribute vec4 a_Position;
            void main() {
                gl_Position = a_Position;
            });

        static const char* f_shader_str = SHADER(
            precision mediump float;
            uniform vec4 u_color;
            void main() {
                gl_FragColor = u_color;
            });

        GLuint program = GLTestHelper::LoadProgram(v_shader_str, f_shader_str);
        glUseProgram(program);

        GLuint position_loc = glGetAttribLocation(program, "a_Position");

        GLTestHelper::SetupUnitQuad(position_loc);

        GLuint color_loc = glGetUniformLocation(program, "u_color");
        glUniform4f(
            color_loc,
            color[0] / 255.0f,
            color[1] / 255.0f,
            color[2] / 255.0f,
            color[3] / 255.0f);
    }

    void TestDraw(int size)
    {
        uint8 expected_clear[] = {
            127,
            0,
            255,
            0,
        };
        glClearColor(0.5f, 0.0f, 1.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, size, size, 1, expected_clear));
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }

} // anonymous namespace

// http://crbug.com/281565
TEST_F(GLVirtualContextsTest, Basic)
{
    struct TestInfo {
        int size;
        uint8 color[4];
        GLManager* manager;
    };
    const int kNumTests = 3;
    TestInfo tests[] = {
        {
            kSize0,
            {
                255,
                0,
                0,
                0,
            },
            &gl_real_,
        },
        {
            kSize1,
            {
                0,
                255,
                0,
                0,
            },
            &gl1_,
        },
        {
            kSize2,
            {
                0,
                0,
                255,
                0,
            },
            &gl2_,
        },
    };

    for (int ii = 0; ii < kNumTests; ++ii) {
        const TestInfo& test = tests[ii];
        GLManager* gl_manager = test.manager;
        gl_manager->MakeCurrent();
        SetupSimpleShader(test.color);
    }

    for (int ii = 0; ii < kNumTests; ++ii) {
        const TestInfo& test = tests[ii];
        GLManager* gl_manager = test.manager;
        gl_manager->MakeCurrent();
        TestDraw(test.size);
    }

    for (int ii = 0; ii < kNumTests; ++ii) {
        const TestInfo& test = tests[ii];
        GLManager* gl_manager = test.manager;
        gl_manager->MakeCurrent();
        EXPECT_TRUE(GLTestHelper::CheckPixels(
            0, 0, test.size, test.size, 0, test.color));
    }

    for (int ii = 0; ii < kNumTests; ++ii) {
        const TestInfo& test = tests[ii];
        GLManager* gl_manager = test.manager;
        gl_manager->MakeCurrent();
        GLTestHelper::CheckGLError("no errors", __LINE__);
    }
}

// http://crbug.com/363407
TEST_F(GLVirtualContextsTest, VertexArrayObjectRestore)
{
    GLuint vao1 = 0, vao2 = 0;

    gl1_.MakeCurrent();
    // Set up red quad in vao1.
    glGenVertexArraysOES(1, &vao1);
    glBindVertexArrayOES(vao1);
    SetUpColoredUnitQuad(kFloatRed);
    glFinish();

    gl2_.MakeCurrent();
    // Set up green quad in vao2.
    glGenVertexArraysOES(1, &vao2);
    glBindVertexArrayOES(vao2);
    SetUpColoredUnitQuad(kFloatGreen);
    glFinish();

    gl1_.MakeCurrent();
    // Test to ensure that vao1 is still the active VAO for this context.
    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kSize1, kSize1, 0, kExpectedRed));
    glFinish();
    GLTestHelper::CheckGLError("no errors", __LINE__);

    gl2_.MakeCurrent();
    // Test to ensure that vao2 is still the active VAO for this context.
    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_TRUE(
        GLTestHelper::CheckPixels(0, 0, kSize2, kSize2, 0, kExpectedGreen));
    glFinish();
    GLTestHelper::CheckGLError("no errors", __LINE__);
}

// http://crbug.com/363407
TEST_F(GLVirtualContextsTest, VertexArrayObjectRestoreRebind)
{
    GLuint vao1 = 0, vao2 = 0;

    gl1_.MakeCurrent();
    // Set up red quad in vao1.
    glGenVertexArraysOES(1, &vao1);
    glBindVertexArrayOES(vao1);
    SetUpColoredUnitQuad(kFloatRed);
    glFinish();

    gl2_.MakeCurrent();
    // Set up green quad in new vao2.
    glGenVertexArraysOES(1, &vao2);
    glBindVertexArrayOES(vao2);
    SetUpColoredUnitQuad(kFloatGreen);
    glFinish();

    gl1_.MakeCurrent();
    // Test to ensure that vao1 hasn't been corrupted after rebinding.
    // Bind 0 is required so that bind vao1 is not optimized away in the service.
    glBindVertexArrayOES(0);
    glBindVertexArrayOES(vao1);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kSize1, kSize1, 0, kExpectedRed));
    glFinish();
    GLTestHelper::CheckGLError("no errors", __LINE__);

    gl2_.MakeCurrent();
    // Test to ensure that vao1 hasn't been corrupted after rebinding.
    // Bind 0 is required so that bind vao2 is not optimized away in the service.
    glBindVertexArrayOES(0);
    glBindVertexArrayOES(vao2);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_TRUE(
        GLTestHelper::CheckPixels(0, 0, kSize2, kSize2, 0, kExpectedGreen));
    glFinish();

    GLTestHelper::CheckGLError("no errors", __LINE__);
}

// http://crbug.com/363407
TEST_F(GLVirtualContextsTest, VertexArrayObjectRestoreDefault)
{
    gl1_.MakeCurrent();
    // Set up red quad in default VAO.
    SetUpColoredUnitQuad(kFloatRed);
    glFinish();

    gl2_.MakeCurrent();
    // Set up green quad in default VAO.
    SetUpColoredUnitQuad(kFloatGreen);
    glFinish();

    // Gen & bind a non-default VAO.
    GLuint vao;
    glGenVertexArraysOES(1, &vao);
    glBindVertexArrayOES(vao);
    glFinish();

    gl1_.MakeCurrent();
    // Test to ensure that default VAO on gl1_ is still valid.
    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kSize1, kSize1, 0, kExpectedRed));
    glFinish();

    gl2_.MakeCurrent();
    // Test to ensure that default VAO on gl2_ is still valid.
    // This tests that a default VAO is restored even when it's not currently
    // bound during the context switch.
    glBindVertexArrayOES(0);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_TRUE(
        GLTestHelper::CheckPixels(0, 0, kSize2, kSize2, 0, kExpectedGreen));
    glFinish();

    GLTestHelper::CheckGLError("no errors", __LINE__);
}

TEST_F(GLVirtualContextsTest, VirtualQueries)
{
    const GLenum query_targets[] = {
        GL_ANY_SAMPLES_PASSED_EXT,
        GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT,
        GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM,
        GL_COMMANDS_COMPLETED_CHROMIUM,
        GL_COMMANDS_ISSUED_CHROMIUM,
        GL_GET_ERROR_QUERY_CHROMIUM,
        GL_LATENCY_QUERY_CHROMIUM,
        GL_TIME_ELAPSED_EXT,
    };

    for (GLenum query_target : query_targets) {
        GLuint query1 = 0;
        gl1_.MakeCurrent();
        glGenQueriesEXT(1, &query1);
        glBeginQueryEXT(query_target, query1);
        const GLenum begin_error = glGetError();
        if (GL_INVALID_OPERATION == begin_error) {
            // Not supported, simply skip.
            glDeleteQueriesEXT(1, &query1);
            continue;
        }
        ASSERT_TRUE(GL_NO_ERROR == begin_error);

        GLuint query2 = 0;
        gl2_.MakeCurrent();
        glGenQueriesEXT(1, &query2);
        glBeginQueryEXT(query_target, query2);
        EXPECT_TRUE(GL_NO_ERROR == glGetError())
            << "Virtualized Query " << query_target << " failed.";

        gl1_.MakeCurrent();
        glEndQueryEXT(query_target);
        glFinish();

        GLuint query1_available = 0;
        glGetQueryObjectuivEXT(query1, GL_QUERY_RESULT_AVAILABLE_EXT,
            &query1_available);
        EXPECT_TRUE(query1_available);

        glDeleteQueriesEXT(1, &query1);
        GLTestHelper::CheckGLError("no errors", __LINE__);

        gl2_.MakeCurrent();
        glEndQueryEXT(query_target);
        glFinish();

        GLuint query2_available = 0;
        glGetQueryObjectuivEXT(query2, GL_QUERY_RESULT_AVAILABLE_EXT,
            &query2_available);
        EXPECT_TRUE(query2_available);

        glDeleteQueriesEXT(1, &query2);
        GLTestHelper::CheckGLError("no errors", __LINE__);
    }
}

} // namespace gpu
